use moss::from_attrs::FromAttrs;
use moss::from_meta::FromMeta;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use syn::DeriveInput;
use syn::Expr;
use syn::Fields;
use syn::Result;

#[derive(FromMeta)]
#[name("status_code")]
pub enum StatusCodeAttr {
    #[strip]
    Part {
        status: Option<Box<Expr>>,
        code: Option<Box<Expr>>,
        cause: Option<Box<Expr>>,
    },
    #[strip]
    From(Box<syn::Expr>),
}

#[derive(FromAttrs)]
pub struct StructAttrs {
    pub status_code: Option<StatusCodeAttr>,
}

#[derive(FromMeta)]
#[name("from")]
pub struct FromAttr;

#[derive(FromAttrs)]
pub struct VariantAttrs {
    pub status_code: Option<StatusCodeAttr>,
}

#[derive(FromAttrs)]
pub struct FieldAttrs {
    pub from: Option<FromAttr>,
}

pub fn error_expand(input: &mut DeriveInput) -> Result<TokenStream> {
    let expanded;
    let self_ident = &input.ident;
    let api_error_path = quote!(apikit::ApiErrorItem);
    let api_errors_path = quote!(apikit::ApiErrors);
    let mut errors_token = quote!();
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let display_token = quote!(let display_ = value.to_string(););

    match &input.data {
        syn::Data::Struct(_) => {
            let attrs = StructAttrs::try_from(&input.attrs)?;
            let mut status_token = quote!(500);
            let mut code_token = quote!("500".into());
            let mut from_token = quote!();
            let mut cause_token: Option<TokenStream> = None;
            if let Some(attr) = attrs.status_code {
                match attr {
                    StatusCodeAttr::Part {
                        status,
                        code,
                        cause,
                    } => {
                        if let Some(status) = status {
                            status_token = quote!(#status);
                        }
                        if let Some(code) = code {
                            code_token = quote!(#code.into());
                        }
                        if let Some(cause) = cause {
                            cause_token = Some(quote!(#cause.into()));
                        }
                    }
                    StatusCodeAttr::From(expr) => {
                        from_token = quote!(let api_error_item = #api_error_path::from(#expr););
                        status_token = quote!(api_error_item.status);
                        code_token = quote!(api_error_item.code);
                        cause_token = Some(quote!(api_error_item.cause));
                    }
                }
            }
            if cause_token.is_none() {
                cause_token = Some(quote! {
                    #api_error_path::cause_from_status(#status_token)
                });
            }
            errors_token = quote! {
                #from_token
                api_errors.push(
                    #api_error_path {
                        status: #status_token,
                        code: #code_token,
                        cause: #cause_token,
                    }
                );
            };
            expanded = quote! {
                impl #impl_generics From<#self_ident #ty_generics> for #api_error_path #where_clause {

                    fn from(value: #self_ident #ty_generics) -> Self {
                        #display_token
                        #from_token
                        Self {
                            status: #status_token,
                            code: #code_token,
                            cause: display_,
                        }
                    }
                }

                #[cfg(feature = "doc")]
                impl #impl_generics #api_errors_path for #self_ident #ty_generics #where_clause {

                    fn api_errors() -> Vec<#api_error_path> {
                        let mut api_errors = vec![];
                        #errors_token
                        api_errors
                    }
                }
            };
        }
        syn::Data::Enum(data_enum) => {
            let mut variant_item_token = quote!();
            for variant in &data_enum.variants {
                let ident = &variant.ident;
                let attrs = VariantAttrs::try_from(&variant.attrs)?;
                let mut status_token = quote!(500);
                let mut code_token = quote!("500".into());
                let mut fieds_var_token = quote!();
                let mut from_token = quote!();
                let mut cause_token: Option<TokenStream> = None;
                let mut sep_status = false;
                let mut sep_code = false;
                let mut extend_error_token: Option<TokenStream> = None;
                for (index, field) in variant.fields.iter().enumerate() {
                    let field_ident = match &field.ident {
                        Some(field) => field,
                        None => &format_ident!("_f{index}"),
                    };
                    fieds_var_token = quote! {
                        #fieds_var_token #field_ident,
                    };
                    let attrs = FieldAttrs::try_from(&field.attrs)?;
                    if attrs.from.is_some() {
                        from_token = quote! {
                            let api_error_item = #api_error_path::from(#field_ident);
                        };
                        status_token = quote!(api_error_item.status);
                        code_token = quote!(api_error_item.code);

                        let field_ty = &field.ty;
                        extend_error_token = Some(
                            quote! {api_errors.extend(<#field_ty as apikit::ApiErrors>::api_errors());},
                        );
                    }
                }
                if let Some(attr) = &attrs.status_code {
                    match attr {
                        StatusCodeAttr::Part {
                            status,
                            code,
                            cause,
                        } => {
                            if let Some(status) = status {
                                status_token = quote!(#status);
                                sep_status = true;
                            }
                            if let Some(code) = code {
                                code_token = quote!(#code.into());
                                sep_code = true;
                            }
                            if let Some(cause) = cause {
                                cause_token = Some(quote!(#cause.into()));
                            }
                        }
                        StatusCodeAttr::From(expr) => {
                            from_token = quote!(let api_error_item = #api_error_path::from(#expr););
                            status_token = quote!(api_error_item.status);
                            code_token = quote!(api_error_item.code);
                            cause_token = Some(quote!(api_error_item.cause));
                        }
                    }
                }
                if sep_status && sep_code {
                    from_token = quote!();
                }
                if let Fields::Unnamed(_) = variant.fields {
                    fieds_var_token = quote! {(#fieds_var_token)};
                } else {
                    fieds_var_token = quote!({#fieds_var_token});
                }
                variant_item_token = quote! {
                    #variant_item_token
                    #self_ident #ty_generics::#ident #fieds_var_token => {
                        #from_token
                        Self {
                            status: #status_token,
                            code: #code_token,
                            cause: display_,
                        }
                    },
                };
                if cause_token.is_none() {
                    cause_token = Some(quote! {
                        #api_error_path::cause_from_status(#status_token)
                    });
                }
                if attrs.status_code.is_none() && extend_error_token.is_some() {
                    errors_token = quote! {
                        #errors_token
                        #extend_error_token
                    };
                } else {
                    errors_token = quote! {
                        #errors_token
                        #from_token
                        api_errors.push(
                            #api_error_path {
                                status: #status_token,
                                code: #code_token,
                                cause: #cause_token,
                            }
                        );
                    };
                }
            }

            expanded = quote! {
                impl #impl_generics From<#self_ident #ty_generics> for #api_error_path #where_clause {

                    fn from(value: #self_ident #ty_generics) -> Self {
                        #display_token
                        match value {
                            #variant_item_token
                        }
                    }
                }

                #[cfg(feature = "doc")]
                impl #impl_generics #api_errors_path for #self_ident #ty_generics #where_clause {

                    fn api_errors() -> Vec<#api_error_path> {
                        let mut api_errors = vec![];
                        #errors_token
                        api_errors
                    }
                }
            };
        }
        syn::Data::Union(_) => return Err(moss::error::Error::unexpected_type("union").into()),
    }
    Ok(expanded)
}
